Micron Document
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| SparkN0de-git | SparkN0de |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Node / ReticulumProjects / MeshChatX.git / files / meshchatx / src / backend / persistent_log_handler.py

Displaying Raw • Download


meshchatx/src/backend/persistent_log_handler.py f10a29d21a0da3ae8eea27ea62b985b9198d05fb (f10a29d2) Text, 9.54 KB

Tff7b72import T7ee787collections
Tff7b72import T7ee787logging
Tff7b72import T7ee787math
Tff7b72import T7ee787re
Tff7b72import T7ee787threading
Tff7b72import T7ee787time
Tff7b72from T7ee787datetime Tff7b72import Te6edf3UTCTb4b4b4, Te6edf3datetime

Te6edf3_LOG_LEVELS Tff7b72= Tb4b4b4(Ta5d6ff"Ta5d6ffDEBUGTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffINFOTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffWARNINGTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffERRORTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffCRITICALTa5d6ff"Tb4b4b4)


Tff7b72class T56d364PersistentLogHandlerTb4b4b4(Te6edf3loggingTff7b72.Td2a8ffHandlerTb4b4b4)Tb4b4b4:
Tff7b72def Tff7b72__init__Tb4b4b4(Tff7b72selfTb4b4b4, Te6edf3databaseTff7b72=Tff7b72NoneTb4b4b4, Te6edf3capacityTff7b72=T79c0ff5000Tb4b4b4, Te6edf3flush_intervalTff7b72=T79c0ff5Tb4b4b4)Tb4b4b4:
Tffa657superTb4b4b4(Tb4b4b4)Tff7b72.Td2a8ff__init__Tb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffdatabase Tff7b72= Te6edf3database
Tff7b72selfTff7b72.Td2a8fflogs_buffer Tff7b72= Te6edf3collectionsTff7b72.Td2a8ffdequeTb4b4b4(Te6edf3maxlenTff7b72=Te6edf3capacityTb4b4b4)
Tff7b72selfTff7b72.Td2a8ffflush_interval Tff7b72= Te6edf3flush_interval
Tff7b72selfTff7b72.Td2a8fflast_flush_time Tff7b72= Te6edf3timeTff7b72.Td2a8fftimeTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8fflock Tff7b72= Te6edf3threadingTff7b72.Td2a8ffRLockTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffflush_lock Tff7b72= Te6edf3threadingTff7b72.Td2a8ffLockTb4b4b4(Tb4b4b4)

T8b949e# Anomaly detection state
Tff7b72selfTff7b72.Td2a8ffrecent_messages Tff7b72= Te6edf3collectionsTff7b72.Td2a8ffdequeTb4b4b4(Te6edf3maxlenTff7b72=T79c0ff100Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffflooding_threshold Tff7b72= T79c0ff20 T8b949e# messages per second
Tff7b72selfTff7b72.Td2a8ffrepeat_threshold Tff7b72= T79c0ff5 T8b949e# identical messages in a row
Tff7b72selfTff7b72.Td2a8ffmessage_counts Tff7b72= Te6edf3collectionsTff7b72.Td2a8ffdefaultdictTb4b4b4(Tffa657intTb4b4b4)
Tff7b72selfTff7b72.Td2a8fflast_reset_time Tff7b72= Te6edf3timeTff7b72.Td2a8fftimeTb4b4b4(Tb4b4b4)

T8b949e# UA and IP tracking
Tff7b72selfTff7b72.Td2a8ffknown_ips Tff7b72= Tffa657setTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffknown_uas Tff7b72= Tffa657setTb4b4b4(Tb4b4b4)

T8b949e# Entropy tracking — level counts over a sliding 60s window
Tff7b72selfTff7b72.Td2a8ff_level_events Tff7b72= Te6edf3collectionsTff7b72.Td2a8ffdequeTb4b4b4(Te6edf3maxlenTff7b72=T79c0ff10000Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_error_events Tff7b72= Te6edf3collectionsTff7b72.Td2a8ffdequeTb4b4b4(Te6edf3maxlenTff7b72=T79c0ff10000Tb4b4b4)

Tff7b72def Td2a8ffset_databaseTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3databaseTb4b4b4)Tb4b4b4:
Tff7b72with Tff7b72selfTff7b72.Td2a8fflockTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffdatabase Tff7b72= Te6edf3database

Tff7b72def Td2a8ffemitTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3recordTb4b4b4)Tb4b4b4:
Tff7b72tryTb4b4b4:
Te6edf3msg Tff7b72= Tff7b72selfTff7b72.Td2a8ffformatTb4b4b4(Te6edf3recordTb4b4b4)
Te6edf3timestamp Tff7b72= Te6edf3datetimeTff7b72.Td2a8ffnowTb4b4b4(Te6edf3UTCTb4b4b4)Tff7b72.Td2a8fftimestampTb4b4b4(Tb4b4b4)

Te6edf3is_anomalyTb4b4b4, Te6edf3anomaly_type Tff7b72= Tff7b72selfTff7b72.Td2a8ff_detect_anomalyTb4b4b4(Te6edf3recordTb4b4b4, Te6edf3msgTb4b4b4, Te6edf3timestampTb4b4b4)

Te6edf3log_entry Tff7b72= Tb4b4b4{
Ta5d6ff"Ta5d6fftimestampTa5d6ff"Tb4b4b4: Te6edf3timestampTb4b4b4,
Ta5d6ff"Ta5d6fflevelTa5d6ff"Tb4b4b4: Te6edf3recordTff7b72.Td2a8fflevelnameTb4b4b4,
Ta5d6ff"Ta5d6ffmoduleTa5d6ff"Tb4b4b4: Te6edf3recordTff7b72.Td2a8ffmoduleTb4b4b4,
Ta5d6ff"Ta5d6ffmessageTa5d6ff"Tb4b4b4: Te6edf3msgTb4b4b4,
Ta5d6ff"Ta5d6ffis_anomalyTa5d6ff"Tb4b4b4: T79c0ff1 Tff7b72if Te6edf3is_anomaly Tff7b72else T79c0ff0Tb4b4b4,
Ta5d6ff"Ta5d6ffanomaly_typeTa5d6ff"Tb4b4b4: Te6edf3anomaly_typeTb4b4b4,
Tb4b4b4}

Tff7b72with Tff7b72selfTff7b72.Td2a8fflockTb4b4b4:
Tff7b72selfTff7b72.Td2a8fflogs_bufferTff7b72.Td2a8ffappendTb4b4b4(Te6edf3log_entryTb4b4b4)
Te6edf3now_mono Tff7b72= Te6edf3timeTff7b72.Td2a8ffmonotonicTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_level_eventsTff7b72.Td2a8ffappendTb4b4b4(Tb4b4b4(Te6edf3now_monoTb4b4b4, Te6edf3recordTff7b72.Td2a8fflevelnameTb4b4b4)Tb4b4b4)
Tff7b72if Te6edf3recordTff7b72.Td2a8fflevelno Tff7b72>Tff7b72= Te6edf3loggingTff7b72.Td2a8ffERRORTb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_error_eventsTff7b72.Td2a8ffappendTb4b4b4(Te6edf3now_monoTb4b4b4)

T8b949e# Periodically flush to database if available
Tff7b72if Tff7b72selfTff7b72.Td2a8ffdatabase Tff7b72and Tb4b4b4(
Te6edf3timeTff7b72.Td2a8fftimeTb4b4b4(Tb4b4b4) Tff7b72- Tff7b72selfTff7b72.Td2a8fflast_flush_time Tff7b72> Tff7b72selfTff7b72.Td2a8ffflush_interval
Tb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_flush_to_dbTb4b4b4(Tb4b4b4)

Tff7b72except Tf85149ExceptionTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffhandleErrorTb4b4b4(Te6edf3recordTb4b4b4)

Tff7b72def Td2a8ff_detect_access_anomalyTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3messageTb4b4b4)Tb4b4b4:
T8b949e"""Detect anomalies in aiohttp access logs."""
T8b949e# Regex to extract IP and User-Agent from aiohttp access log
T8b949e# Format: IP [date] "GET ..." status size "referer" "User-Agent"
Te6edf3match Tff7b72= Te6edf3reTff7b72.Td2a8ffsearchTb4b4b4(
Ta5d6ffrTa5d6ff"Ta5d6ff^([Ta5d6ff\Ta5d6ffdTa5d6ff\Ta5d6ff.Ta5d6ff\Ta5d6ff:]+) .* Tffea00\"Ta5d6ff[^Tffea00\"Ta5d6ff]+Tffea00\"Ta5d6ff Ta5d6ff\Ta5d6ffd+ Ta5d6ff\Ta5d6ffd+ Tffea00\"Ta5d6ff[^Tffea00\"Ta5d6ff]*Tffea00\"Ta5d6ff Tffea00\"Ta5d6ff([^Tffea00\"Ta5d6ff]+)Tffea00\"Ta5d6ff"Tb4b4b4,
Te6edf3messageTb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3matchTb4b4b4:
Te6edf3ip Tff7b72= Te6edf3matchTff7b72.Td2a8ffgroupTb4b4b4(T79c0ff1Tb4b4b4)
Te6edf3ua Tff7b72= Te6edf3matchTff7b72.Td2a8ffgroupTb4b4b4(T79c0ff2Tb4b4b4)

Tff7b72with Tff7b72selfTff7b72.Td2a8fflockTb4b4b4:
Te6edf3is_anomaly Tff7b72= Tff7b72False
Te6edf3anomaly_type Tff7b72= Tff7b72None

T8b949e# Detect if this is a different UA or IP from what we've seen recently
Tff7b72if Tffa657lenTb4b4b4(Tff7b72selfTff7b72.Td2a8ffknown_ipsTb4b4b4) Tff7b72> T79c0ff0 Tff7b72and Te6edf3ip Tff7b72not Tff7b72in Tff7b72selfTff7b72.Td2a8ffknown_ipsTb4b4b4:
Te6edf3is_anomaly Tff7b72= Tff7b72True
Te6edf3anomaly_type Tff7b72= Ta5d6ff"Ta5d6ffmulti_ipTa5d6ff"

Tff7b72if Tffa657lenTb4b4b4(Tff7b72selfTff7b72.Td2a8ffknown_uasTb4b4b4) Tff7b72> T79c0ff0 Tff7b72and Te6edf3ua Tff7b72not Tff7b72in Tff7b72selfTff7b72.Td2a8ffknown_uasTb4b4b4:
Te6edf3is_anomaly Tff7b72= Tff7b72True
Tff7b72if Te6edf3anomaly_typeTb4b4b4:
Te6edf3anomaly_type Tff7b72= Ta5d6ff"Ta5d6ffmulti_ip_uaTa5d6ff"
Tff7b72elseTb4b4b4:
Te6edf3anomaly_type Tff7b72= Ta5d6ff"Ta5d6ffmulti_uaTa5d6ff"

Tff7b72selfTff7b72.Td2a8ffknown_ipsTff7b72.Td2a8ffaddTb4b4b4(Te6edf3ipTb4b4b4)
Tff7b72selfTff7b72.Td2a8ffknown_uasTff7b72.Td2a8ffaddTb4b4b4(Te6edf3uaTb4b4b4)

T8b949e# Cap the tracking sets to prevent memory growth
Tff7b72if Tffa657lenTb4b4b4(Tff7b72selfTff7b72.Td2a8ffknown_ipsTb4b4b4) Tff7b72> T79c0ff100Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffknown_ipsTff7b72.Td2a8ffclearTb4b4b4(Tb4b4b4)
Tff7b72if Tffa657lenTb4b4b4(Tff7b72selfTff7b72.Td2a8ffknown_uasTb4b4b4) Tff7b72> T79c0ff100Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffknown_uasTff7b72.Td2a8ffclearTb4b4b4(Tb4b4b4)

Tff7b72return Te6edf3is_anomalyTb4b4b4, Te6edf3anomaly_type

Tff7b72return Tff7b72FalseTb4b4b4, Tff7b72None

Tff7b72def Td2a8ff_detect_anomalyTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3recordTb4b4b4, Te6edf3messageTb4b4b4, Te6edf3timestampTb4b4b4)Tb4b4b4:
T8b949e# 1. Access anomaly detection (UA/IP) - checked for all levels of aiohttp.access
Tff7b72if Te6edf3recordTff7b72.Td2a8ffname Tff7b72== Ta5d6ff"Ta5d6ffaiohttp.accessTa5d6ff"Tb4b4b4:
Te6edf3is_acc_anomalyTb4b4b4, Te6edf3acc_type Tff7b72= Tff7b72selfTff7b72.Td2a8ff_detect_access_anomalyTb4b4b4(Te6edf3messageTb4b4b4)
Tff7b72if Te6edf3is_acc_anomalyTb4b4b4:
Tff7b72return Tff7b72TrueTb4b4b4, Te6edf3acc_type

T8b949e# Only detect other anomalies for WARNING level and above
Tff7b72if Te6edf3recordTff7b72.Td2a8fflevelno Tff7b72< Te6edf3loggingTff7b72.Td2a8ffWARNINGTb4b4b4:
Tff7b72return Tff7b72FalseTb4b4b4, Tff7b72None

Te6edf3now Tff7b72= Te6edf3timeTff7b72.Td2a8fftimeTb4b4b4(Tb4b4b4)

T8b949e# 1. Detect Log Flooding
Tff7b72if Te6edf3now Tff7b72- Tff7b72selfTff7b72.Td2a8fflast_reset_time Tff7b72> T79c0ff1.0Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffmessage_countsTff7b72.Td2a8ffclearTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8fflast_reset_time Tff7b72= Te6edf3now

Tff7b72selfTff7b72.Td2a8ffmessage_countsTb4b4b4[Ta5d6ff"Ta5d6fftotalTa5d6ff"Tb4b4b4] Tff7b72+Tff7b72= T79c0ff1
Tff7b72if Tff7b72selfTff7b72.Td2a8ffmessage_countsTb4b4b4[Ta5d6ff"Ta5d6fftotalTa5d6ff"Tb4b4b4] Tff7b72> Tff7b72selfTff7b72.Td2a8ffflooding_thresholdTb4b4b4:
Tff7b72return Tff7b72TrueTb4b4b4, Ta5d6ff"Ta5d6fffloodingTa5d6ff"

T8b949e# 2. Detect Repeats
Tff7b72if Tffa657lenTb4b4b4(Tff7b72selfTff7b72.Td2a8ffrecent_messagesTb4b4b4) Tff7b72> T79c0ff0Tb4b4b4:
Te6edf3repeat_count Tff7b72= T79c0ff0
Tff7b72for Te6edf3prev_msg Tff7b72in Tffa657reversedTb4b4b4(Tff7b72selfTff7b72.Td2a8ffrecent_messagesTb4b4b4)Tb4b4b4:
Tff7b72if Te6edf3prev_msg Tff7b72== Te6edf3messageTb4b4b4:
Te6edf3repeat_count Tff7b72+Tff7b72= T79c0ff1
Tff7b72elseTb4b4b4:
Tff7b72break

Tff7b72if Te6edf3repeat_count Tff7b72>Tff7b72= Tff7b72selfTff7b72.Td2a8ffrepeat_thresholdTb4b4b4:
Tff7b72return Tff7b72TrueTb4b4b4, Ta5d6ff"Ta5d6ffrepeatTa5d6ff"

Tff7b72selfTff7b72.Td2a8ffrecent_messagesTff7b72.Td2a8ffappendTb4b4b4(Te6edf3messageTb4b4b4)
Tff7b72return Tff7b72FalseTb4b4b4, Tff7b72None

Tff7b72def Td2a8ff_flush_to_dbTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72not Tff7b72selfTff7b72.Td2a8ffdatabaseTb4b4b4:
Tff7b72return

T8b949e# Ensure only one thread flushes at a time
Tff7b72if Tff7b72not Tff7b72selfTff7b72.Td2a8ffflush_lockTff7b72.Td2a8ffacquireTb4b4b4(Te6edf3blockingTff7b72=Tff7b72FalseTb4b4b4)Tb4b4b4:
Tff7b72return

Tff7b72tryTb4b4b4:
Te6edf3items_to_flush Tff7b72= Tb4b4b4[Tb4b4b4]
Tff7b72with Tff7b72selfTff7b72.Td2a8fflockTb4b4b4:
Tff7b72while Tff7b72selfTff7b72.Td2a8fflogs_bufferTb4b4b4:
Te6edf3items_to_flushTff7b72.Td2a8ffappendTb4b4b4(Tff7b72selfTff7b72.Td2a8fflogs_bufferTff7b72.Td2a8ffpopleftTb4b4b4(Tb4b4b4)Tb4b4b4)

Tff7b72if Tff7b72not Te6edf3items_to_flushTb4b4b4:
Tff7b72return

T8b949e# Batch insert for speed
Tff7b72for Te6edf3entry Tff7b72in Te6edf3items_to_flushTb4b4b4:
Tff7b72tryTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffdatabaseTff7b72.Td2a8ffdebug_logsTff7b72.Td2a8ffinsert_logTb4b4b4(
Te6edf3levelTff7b72=Te6edf3entryTb4b4b4[Ta5d6ff"Ta5d6fflevelTa5d6ff"Tb4b4b4]Tb4b4b4,
Te6edf3moduleTff7b72=Te6edf3entryTb4b4b4[Ta5d6ff"Ta5d6ffmoduleTa5d6ff"Tb4b4b4]Tb4b4b4,
Te6edf3messageTff7b72=Te6edf3entryTb4b4b4[Ta5d6ff"Ta5d6ffmessageTa5d6ff"Tb4b4b4]Tb4b4b4,
Te6edf3is_anomalyTff7b72=Te6edf3entryTb4b4b4[Ta5d6ff"Ta5d6ffis_anomalyTa5d6ff"Tb4b4b4]Tb4b4b4,
Te6edf3anomaly_typeTff7b72=Te6edf3entryTb4b4b4[Ta5d6ff"Ta5d6ffanomaly_typeTa5d6ff"Tb4b4b4]Tb4b4b4,
Tb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffError inserting log: Tffd700{Te6edf3eTffd700}Ta5d6ff"Tb4b4b4)

T8b949e# Periodic cleanup of old logs (only every 100 flushes or similar?
T8b949e# for now let's just keep it here but it should be fast)
Tff7b72tryTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffdatabaseTff7b72.Td2a8ffdebug_logsTff7b72.Td2a8ffcleanup_old_logsTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffError cleaning up logs: Tffd700{Te6edf3eTffd700}Ta5d6ff"Tb4b4b4)

Tff7b72selfTff7b72.Td2a8fflast_flush_time Tff7b72= Te6edf3timeTff7b72.Td2a8fftimeTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffFailed to flush logs to database: Tffd700{Te6edf3eTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72finallyTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffflush_lockTff7b72.Td2a8ffreleaseTb4b4b4(Tb4b4b4)

Tff7b72def Td2a8ffget_logsTb4b4b4(
Tff7b72selfTb4b4b4,
Te6edf3limitTff7b72=T79c0ff100Tb4b4b4,
Te6edf3offsetTff7b72=T79c0ff0Tb4b4b4,
Te6edf3searchTff7b72=Tff7b72NoneTb4b4b4,
Te6edf3levelTff7b72=Tff7b72NoneTb4b4b4,
Te6edf3moduleTff7b72=Tff7b72NoneTb4b4b4,
Te6edf3is_anomalyTff7b72=Tff7b72NoneTb4b4b4,
Tb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72selfTff7b72.Td2a8ffdatabaseTb4b4b4:
T8b949e# Flush current buffer first to ensure we have latest logs
Tff7b72selfTff7b72.Td2a8ff_flush_to_dbTb4b4b4(Tb4b4b4)

Tff7b72with Tff7b72selfTff7b72.Td2a8fflockTb4b4b4:
Tff7b72if Tff7b72selfTff7b72.Td2a8ffdatabaseTb4b4b4:
Tff7b72return Tff7b72selfTff7b72.Td2a8ffdatabaseTff7b72.Td2a8ffdebug_logsTff7b72.Td2a8ffget_logsTb4b4b4(
Te6edf3limitTff7b72=Te6edf3limitTb4b4b4,
Te6edf3offsetTff7b72=Te6edf3offsetTb4b4b4,
Te6edf3searchTff7b72=Te6edf3searchTb4b4b4,
Te6edf3levelTff7b72=Te6edf3levelTb4b4b4,
Te6edf3moduleTff7b72=Te6edf3moduleTb4b4b4,
Te6edf3is_anomalyTff7b72=Te6edf3is_anomalyTb4b4b4,
Tb4b4b4)
T8b949e# Fallback to in-memory buffer if DB not yet available
Te6edf3logs Tff7b72= Tffa657listTb4b4b4(Tff7b72selfTff7b72.Td2a8fflogs_bufferTb4b4b4)
Tff7b72if Te6edf3searchTb4b4b4:
Te6edf3logs Tff7b72= Tb4b4b4[
Te6edf3log
Tff7b72for Te6edf3log Tff7b72in Te6edf3logs
Tff7b72if Te6edf3searchTff7b72.Td2a8fflowerTb4b4b4(Tb4b4b4) Tff7b72in Te6edf3logTb4b4b4[Ta5d6ff"Ta5d6ffmessageTa5d6ff"Tb4b4b4]Tff7b72.Td2a8fflowerTb4b4b4(Tb4b4b4)
Tff7b72or Te6edf3searchTff7b72.Td2a8fflowerTb4b4b4(Tb4b4b4) Tff7b72in Te6edf3logTb4b4b4[Ta5d6ff"Ta5d6ffmoduleTa5d6ff"Tb4b4b4]Tff7b72.Td2a8fflowerTb4b4b4(Tb4b4b4)
Tb4b4b4]
Tff7b72if Te6edf3levelTb4b4b4:
Te6edf3logs Tff7b72= Tb4b4b4[Te6edf3log Tff7b72for Te6edf3log Tff7b72in Te6edf3logs Tff7b72if Te6edf3logTb4b4b4[Ta5d6ff"Ta5d6fflevelTa5d6ff"Tb4b4b4] Tff7b72== Te6edf3levelTb4b4b4]
Tff7b72if Te6edf3is_anomaly Tff7b72is Tff7b72not Tff7b72NoneTb4b4b4:
Te6edf3logs Tff7b72= Tb4b4b4[
Te6edf3log Tff7b72for Te6edf3log Tff7b72in Te6edf3logs Tff7b72if Te6edf3logTb4b4b4[Ta5d6ff"Ta5d6ffis_anomalyTa5d6ff"Tb4b4b4] Tff7b72== Tb4b4b4(T79c0ff1 Tff7b72if Te6edf3is_anomaly Tff7b72else T79c0ff0Tb4b4b4)
Tb4b4b4]

T8b949e# Sort descending
Te6edf3logsTff7b72.Td2a8ffsortTb4b4b4(Te6edf3keyTff7b72=Tff7b72lambda Te6edf3xTb4b4b4: Te6edf3xTb4b4b4[Ta5d6ff"Ta5d6fftimestampTa5d6ff"Tb4b4b4]Tb4b4b4, Te6edf3reverseTff7b72=Tff7b72TrueTb4b4b4)
Tff7b72return Te6edf3logsTb4b4b4[Te6edf3offset Tb4b4b4: Te6edf3offset Tff7b72+ Te6edf3limitTb4b4b4]

Tff7b72def Td2a8ffget_total_countTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3searchTff7b72=Tff7b72NoneTb4b4b4, Te6edf3levelTff7b72=Tff7b72NoneTb4b4b4, Te6edf3moduleTff7b72=Tff7b72NoneTb4b4b4, Te6edf3is_anomalyTff7b72=Tff7b72NoneTb4b4b4)Tb4b4b4:
Tff7b72with Tff7b72selfTff7b72.Td2a8fflockTb4b4b4:
Tff7b72if Tff7b72selfTff7b72.Td2a8ffdatabaseTb4b4b4:
Tff7b72return Tff7b72selfTff7b72.Td2a8ffdatabaseTff7b72.Td2a8ffdebug_logsTff7b72.Td2a8ffget_total_countTb4b4b4(
Te6edf3searchTff7b72=Te6edf3searchTb4b4b4,
Te6edf3levelTff7b72=Te6edf3levelTb4b4b4,
Te6edf3moduleTff7b72=Te6edf3moduleTb4b4b4,
Te6edf3is_anomalyTff7b72=Te6edf3is_anomalyTb4b4b4,
Tb4b4b4)
Tff7b72return Tffa657lenTb4b4b4(Tff7b72selfTff7b72.Td2a8fflogs_bufferTb4b4b4)

Tf0883e@property
Tff7b72def Td2a8ffcurrent_log_entropyTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
T8b949e"""Shannon entropy over log-level distribution in the last 60 seconds."""
Te6edf3cutoff Tff7b72= Te6edf3timeTff7b72.Td2a8ffmonotonicTb4b4b4(Tb4b4b4) Tff7b72- T79c0ff60.0
Tff7b72with Tff7b72selfTff7b72.Td2a8fflockTb4b4b4:
Te6edf3counts Tff7b72= Tb4b4b4{Te6edf3lvTb4b4b4: T79c0ff0 Tff7b72for Te6edf3lv Tff7b72in Te6edf3_LOG_LEVELSTb4b4b4}
Te6edf3total Tff7b72= T79c0ff0
Tff7b72for Te6edf3tsTb4b4b4, Te6edf3level Tff7b72in Tff7b72selfTff7b72.Td2a8ff_level_eventsTb4b4b4:
Tff7b72if Te6edf3ts Tff7b72>Tff7b72= Te6edf3cutoffTb4b4b4:
Te6edf3countsTb4b4b4[Te6edf3levelTb4b4b4] Tff7b72= Te6edf3countsTff7b72.Td2a8ffgetTb4b4b4(Te6edf3levelTb4b4b4, T79c0ff0Tb4b4b4) Tff7b72+ T79c0ff1
Te6edf3total Tff7b72+Tff7b72= T79c0ff1
Tff7b72if Te6edf3total Tff7b72== T79c0ff0Tb4b4b4:
Tff7b72return T79c0ff0.0
Te6edf3entropy Tff7b72= T79c0ff0.0
Tff7b72for Te6edf3c Tff7b72in Te6edf3countsTff7b72.Td2a8ffvaluesTb4b4b4(Tb4b4b4)Tb4b4b4:
Tff7b72if Te6edf3c Tff7b72> T79c0ff0Tb4b4b4:
Te6edf3p Tff7b72= Te6edf3c Tff7b72/ Te6edf3total
Te6edf3entropy Tff7b72-Tff7b72= Te6edf3p Tff7b72* Te6edf3mathTff7b72.Td2a8fflog2Tb4b4b4(Te6edf3pTb4b4b4)
Tff7b72return Te6edf3entropy

Tf0883e@property
Tff7b72def Td2a8ffcurrent_error_rateTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
T8b949e"""Fraction of log events at ERROR or above in the last 60 seconds."""
Te6edf3cutoff Tff7b72= Te6edf3timeTff7b72.Td2a8ffmonotonicTb4b4b4(Tb4b4b4) Tff7b72- T79c0ff60.0
Tff7b72with Tff7b72selfTff7b72.Td2a8fflockTb4b4b4:
Te6edf3total Tff7b72= Tffa657sumTb4b4b4(T79c0ff1 Tff7b72for Te6edf3tsTb4b4b4, Te6edf3_ Tff7b72in Tff7b72selfTff7b72.Td2a8ff_level_events Tff7b72if Te6edf3ts Tff7b72>Tff7b72= Te6edf3cutoffTb4b4b4)
Te6edf3errors Tff7b72= Tffa657sumTb4b4b4(T79c0ff1 Tff7b72for Te6edf3ts Tff7b72in Tff7b72selfTff7b72.Td2a8ff_error_events Tff7b72if Te6edf3ts Tff7b72>Tff7b72= Te6edf3cutoffTb4b4b4)
Tff7b72if Te6edf3total Tff7b72== T79c0ff0Tb4b4b4:
Tff7b72return T79c0ff0.0
Tff7b72return Te6edf3errors Tff7b72/ Te6edf3total


──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────